الخيوط


الخيوط threads

يعرف الخيط بأنة طريق أو مسلك لتنفيذ التعليمات بشكل مستقل عن الخيوط الاخرى ، لدى Multithreading المقدرة لعمل تخيطات عديدة بحيث يمكن أن يحتوي التطبيق على خيوط عديدة ، وكل خيط تخصص الى مهمة معينة وتنفذ بشكل متوافق مع الخيوط الاخرى ، تختلف Multithreading تماماً عن تعدد العمليات multiprocessing فبدلاً من كون توطين عمليتين أو أكثر في فضاء الذاكرة وكل منها لديها مساحة خاص بها ولديها قائدة الإشارات signal handlers و المتغيرات العامة الخاصة ، فان برامج multithreaded لديها عملية واحدة التي تشغل فيها العديد من الخيوط المنفذة ، وكل خيط من هذة الخيوط تشغل بشكل مستقل وتستطيع عمل تكرار أو انجاز عملية I/O بدون القلق على الخيوط الاخرى التي تشغل ،كما ان الخيوط مشتركة بالمتغيرات العامة ومقابض الملفات والمصادر الاخرى فهذة الاشتراكات بين المصادر تجعل التفاعل أكثر بكثير من أستعمال عمليات منفصلة بواسطة الدالة fork ، مما تساعد على إنشاء إمكانية اخرى للإتصال بالمصادر، وايضاً لدى الخيوط الحديثة parent thread ولديها المعرفات thread IDs الفريدة الخاصة بكل خيط بدلاً من process IDs مع تعدد العمليات ، وبالرغم من ان برمجة تعدد الخيوط بسيط لبرامجك بشكل من الاشكال أو في بعض الطرق لكنة معقد في أشياء اخر فمثلاً اذا حاولنا تعديل متغير في خيطين في نفس الوقت فأن الناتج قد يكون غير متوقع ولهذا السبب فأن قفل المصدر والسيطرة علية تصبح قضية في برامج الخيوط كما سنراه لاحقاً أن شاء الله.

عندما يستحضر البرنامج فأن النظام ينشأ عملية جديدة التي تقوم بدورها بإنشأ خيط وحيد single thread يعمل على تشغيل البرنامج بشكل متسلسل ، فبهذا الخيط يمكن إنشاء مجموعة من الخيوط وكل خيط تشغل في نفس البرنامج في نفس العملية ، في السابق عند تفرع عملية الطفل بواسطة fork تشغل عملية الطفل من برنامجه الأب والنسخة المستنخة مطابقة لذاكرة الأب الأفتراضية ومقابض الملفات ...ولهذة العملية المستنسخة ذاكرة خاصة بها ونستطيع تعديل ذاكرتها وإغلاق المقابض بدون إي تأثير للعملية الأب والعكس بالعكس ، بينما إنشاء خيط اخر فلا شيء ينسخ والخيط الأب والخيط المنشأءة متشاركة في نفس فضاء العنوان (وهذا مايجعلهم اكثر بكثير خفيفة الوزن) للذاكرة ونفس المقابض ...والمصادر الاخرى الاصلية ،وبتعديل إي thread مثلاً بتعديل قيمة المتغير فسوف تعدل تلك القيمة في الخيط الاخر ايضاً، و بشكل مشابة مع المقابض فأن أغلقت المقبض في طرف فسوف لان يقراء في الطرف الاخر

شملت إتفاقية الحاسوب على ان مجموعة التعليمات تدعى برنامج ومجموعة البرامج تدعى تطبيق أو برمجيات software وفي الحقيقة يعتبر مصطلح البرمجيات نظرة قاصرة فالبرمجيات ليست هي برامج الكمبيوتر فقط لكنها ايضا كل التوثيق documentaion المرتبط بها وبيانات التكوين Configuration اللازمة لجعل البرامج تعمل بصورة صحيحة ، المهم يكتب البرنامج الى المعالج processor لتأدية مهمة معينة من أجل عمل محدد ، ومع تعدد المهام فأن العديد من البرامج تشغل آنياً simultaneously أن لكل برنامج منها ينفذ في خيط ، ولاتنسى بأن المعالج تعالج جميع هذة البرامج المخيطة في معالج وحيد single-processor بهذا المعالج الوحيد فأن عملية واحدة فقط يمكن أن تنفذ ويعمل وحدة المعالجة CPU بفتح بين الخيوط بدلاً من معالجة كل عملية في processor منفصل مع تعدد العمليات multiprocessing لذلك فأن تنفذ البرامج على هذا المعالج بالنسبة والمعالج يفتح بشكل فعلي بين المهام ، بالتالي سنجد أن زمن المعالجات سيتوزع بين الخيوط المختلفة ، ومثال للتوضيح نفرض توزيع هذة الأوقات بالمعدل أي أنة إذا وجد خيطان في الذاكرة فإن زمن المعالجة CPU time سيتوزع مناصفة ، وإذا كان هنالك 3 خيوط فإن زمن المعالجة سيتوزع بالثلث لكل خيط وهكذا ..

الزمن الحدث وصول/إنتهاء عدد الخيوط في الذاكرة نسبة زمن المعالجة الزمن المستغرق الزمن المستنفذ للخيط الخيوط الزمن الباقي
10 وصول الخيط 1 - - - - 1 0.2
10.1 وصول الخيط 2 1 100%=1 0.1 0.1 1 0.1
2 0.3
10.2 وصول الخيط 3 2 50%=1/2 0.1 0.05 1 0.05
2 0.25
3 0.1
10.3 وصول الخيط 4 3 33.3%=1/3 0.1 0.33 1 0.017
2 0.217
3 0.067
4 0.04
10.368 إنتهاء الخيط 1 4 25%=1/4 0.068 0.017 2 0.2
3 0.05
4 0.333
10.518 إنتهاء الخيط 3 3 33.3%=1/3 0.15 0.05 2 0.15
4 0.183
10.818 إنتهاء الخيط 2 2 50%=1/2 0.3 0.15 4 0.033
10.848 إنتهاء الخيط 4 1 100%=1 0.033 0.033 - -



الـ Thread متؤفرة على إرصفة اليونكس فكانت ميزة تجربية فأصبحت نسخة متينة جداً في البيرل 5.8 وايضاً متوفرة وقادرة على التخيط في نسخ البيرل التي تأتي مع الونيدوز ، وفي الحقيقة فأن التفرع fork متضمنة مع الخيوط على الونيدوز—فمعرف العملية process ID الراجع بواسطة fork هي في الحقيقة ما هي الا thread ID ، طالما نعرف هذة التحذيرات أو الدعم للبيرل فليس هناك سبب لكتابة تطبيق مخيط threaded ، ايضاً مطورين البيرل سوف يطورن هذة الميزة بتصميم وتخطيط جديد تعرف بالـ interpreter threads (ithreads) التي هي جزء من 5.8 و ستكون جزء من بيرل 6 التي تعد بأن تكون اكثر استقراراً




فحص الخيط المدعمة

لمعرفة توفر برمجة الخيوط في حاسوبك الخاص علينا استخدام الهاش %Config بفحص مفتاح usethreads :

BEGIN {
use Config;
die "Threadbare!\n" unless $Config{usethreads};
}

تخبرنا عن نوعية الخيوط المدعمة لكن إذ لم تكن متواجدة نحتاج فحص ithreads بدلاً من نسخة الخيوط القديمة 5.005 مع مفتاح الهاش useithreads :

BEGIN {
use Config;
die "No interpreter threads!\n" unless $Config{useithreads};
}

ان أبسط طريقة قراءة الوثيقة من نأفذة الاوامر لفحص الخيوط الحالية بمجرد كتابة perldoc يلية اسم الوحدة البرمجية threads ، يبين الخرج ان Thread الحالية لجميع النسخ الخيوط السابقة لها وهي فقط لمعرفة اذا كانت ithread مدعمة في النسخة :


>perldoc threads

تعتبر الوحدة البرمجية threads أساساً في تصحيح الخيوط لمعظم نسخ الخيوط السابقة فأن كان الأساس صحيحا فخطوات البناء التالي تكون بجهد لايرقى إلى جهد الخطوات الأولى ،عموماً الخيوط threads من الوحدات الكائنات الموجهه تمثل الخيوط بكائنات يتم انشاءها بإستخدام الباني new ثم من الممكن استخدام الطرق القديمة للكائن التي تاتي مع Thread تنجز بإلاظافة عدة وظائف خاصة بها ، الى جانب ithreads المتواجده في وحـدة Thread القديمة قابلة للنقل البرامج المكتوبة في بئية التفاعل القديم الى بئية التفاعل الجديد ،في التضمين القديم بيانتها تشارك وتحفظ تلقائياً كما لو كانت private مع جميع الخيوط ithreads وهذة بادرة مفيدة لعمل برامج متعدد الخيوط من دون اي تعقيد خلافاً عن تعدد العمليات التي نستخدام فيها آليات معقدة لإنجاز المشاركة في الذاكرة .

تأتي وحـدة Thread في شكلين وهما الـ pragmatic وApplication-oriented :
يوفر الدعم الإساسي للخيوط بواسطة وحـدات برمجية pragmatic هم :threads و threads::shared ، حيث أن الاخير تعطى الوحدة البرمجية تشاركاًٍ للبيانات بين الخيوط ، تتفاعل هذة الوحدات البرمجية مباشرتاً بميكانيكية ithread لتمكين وتضمين الخيوط ومشاركة البيانات في التطبيق ، بالرغم من أنهم pragmatic الا انها تنجز ايضاً الطرق والدوال (وذلك لبدأ الخيوط أو لتؤقف أو لمعالجة الخيوط ).

اما وحـدات خيط Application-oriented موجودة في فضاء اسم Thread:: مثل وحـدات Thread::Semaphore و Thread::Queue التي من ضمن الوحـدات القياسية للبيرل ، تمنح لنا هذة الوحدات البرمجية تنظيماً للخيوط و السيطرة على تدفق البيانات في التطبيقات المخيطة ويمنع الخيوط من الوصول الى البيانات المشاركة في إسلوب الغير متزامن unsynchronized ، سناقشها في نهاية هذا الفصل ان شاء الله واذا اردت الاطلاع والمشاهدة هناك العديد من الامثلة على CPAN


إنشاء الخيوط

هناك وجة التشابة بين الخيوط و العمليات المتفرعة في عديد من الاشكال ، انه مماثل في مصطلح الإنشاء ، ان الطريقة القياسية لإنشاء الخيط استخدام الطريقة create التي تأخذ مرجع دالة وقائمة الوسطاء للعبور إليها ، بعد ذلك تنفذ الدالة بواسطة الخيط الجديد بينما الخيط الأب parent thread يستلم كائن الخيط كقيمة معادة ، يوضح الكود التالي المقصوص كيفية انشاء الخيط :

use threads;
sub threadsub {
my $self = threads->self;
...
}
my $thread = threads->create(\&threadsub, @args);

الطريقة new هي اسم مستعار ولذا نستطيع ايضاً ان نقول

my $thread = new threads(\&threadsub, @args);

ليس هناك اختلاف في السابق في إنشاء خيط جديد مع دالة threadsub ، تعتبر هذة الدالة كنقطة دخولentry point الخيط الجديد بينما الخيط الرئيسي يستمر علية .هناك طريقة بديلة للإنشاء الخيط بواسطة الدالة async حيث لديها صيغة مماثلة بالدالة المخفية ، بما أن الدالة لايتم إستيرادها في الأفتراضي لذا يتوجب علينا إستيرادها عند إستعمال الوحـدة البرمجية Thread :

use Thread qw(async);
my $thread = async {
my $self = threads->self;
...
};

كما هي الدالة المخفية يجب ان تنتهي بالرمز ; ولانها تحتاج الى البلوك بينما قد يعمل المحتوى بعدم اضافة البلوك للدالة .

يعتمد الإختيار بين create /new / async على طبيعة الخيط المرغوب لكي يبدأ ، الطريقتين create/new هي شكل متماثل في كل الإجزاء متعلقة بالصيغة ، الشيء المختلف إذا كانت ترغب فقط لبدأ الخيط في حالة واحدة في ذلك الوقت عليك إستخدم الدالة async ، بينما create/new تفضل في إستخدامات مختلفة فمثلاً لإستخدام نفس الدالة الخاصة بنا في مختلف الخيوط :

my $thread1 = new threads \&threadsub, $arg1;
my $thread2 = new threads \&threadsub, $arg2;
my $thread3 = new threads \&threadsub, $arg3;

أو يمكن استخدام التكرار وتخزين كائنات الخيوط في مصفوفة

# start a new thread for each argument passed in @ARGV:
@threads;
foreach (@ARGV) {
push @threads, threads->create(\&threadsub, $_);
}

لأن ithreads تعمل نسخة من البيانات في الخيط الأصلية للإنشاء الخيط الجديد ، فلان نحتاج لأخذ إي حدث خاص للإنشاء خيوط جديدة ، والكود الذي ليس thread-aware سيصبح بشكل آلي خيط أمن thread-safe مع ithreads . على اي حال اذا لدينا اي بيانات non-Perl في الخيط ، لايمكن البيرل ان تتعامل معها مباشرتاً — هذا بتأثير خصوصاً من الوحدات البرمجية الممتدة extension modules مع XSUBs — قد تحصل بعض المعضلات ، يمكن لحلها استخدام الطريقة CLONE الخاصة في اي حزمة عند الحاجة للتعامل مع الحالات الخاصة :

package Special::Thread::Module;
sub CLONE {
# handle any special thread cloning needs for this package
}

في هذة الحالة فأن الطريقة تستدعى — في كل حزمة مشحونة التي تعرف أو ترثها —مباشرتاً بعد الخيط ما ينشأ وقبل استدعى نقطة الدخول entry point التابعة له، ونستطيع ايضاً إستعمال هذة الطريقة لضبط البيانات أو تقليصها “thin down” التي لان تحتاج الى تنسخ الى حافظة الذاكرة .



أستدعاء clone فهي معممة عند انشاء التفرع أو الخيوط لكي تمنح للكائن المستدعئ الى تحديد إي من تلك الموارد سوف تشارك بين عملية الإستدعاء وعملية المنشأءة الجديدة ( clone تعني انشاء نسخة مطابقة لعملية الاب للإبن )

معرفات الخيوط

بعدما قمنا ببداء start الخيوط المختلفة حيث الجميع لها نفس الدالة كنقطة دخول، نستطيع تحديد ومعرفة كل جزء على حدا يمكننا ،هذا بطريقتين ، الاولى بواسطة عبور مختلف الوسطاء عندما تبداء كل خيط لوضعهم على المهام المختلفة ، مثلاً في البرنامج التالي يستخدم وحـدة Thread ، يخبر عن الخيط في المتغير $message :

#!/usr/bin/perl
use Thread;
my $thread1 = Thread->new(\&hello, "I am thread 1",3);
my $thread2 = Thread->new(\&hello, "I am thread 2",6);
$_->join foreach ($thread1,$thread2);

sub hello {
    my ($message,$loop) =@_;
    for (1..$loop) { print $message,"\n"; sleep 1; }
}

في البرنامج السابق إنشاء خيط جديد مع تنفذ الدالة hello التي تعبر وسيطين السلاسل النصية والقيمة ، في نجاح الاستدعاء new يسترجع كائن الخيط في الإعادة . يمكن استدعاء الطريقة detach التي تحرر الخيط الرئيسي main thread من اي مسؤؤلية للتعامل معه حيث ان الخيط الرئيسي يتوجب علية إنتظار جميع خيوط الإطفال الفصل قبل الخروج exiting منه ، أوبدلاً منها الخيط الرئيسي ( أو اي خيط اخرى ) يجب ان تستدعى كائن الخيط الطريقة join لإسترجاع القيم المعادة من الدالة عند الخروج من البرنامج أو في وقت اعادة قيمة مرغوب بها من الخيط سنتكلم عنها اكثر فيما بعد في امثلة .

الطريقة الاخرى نستطيع أستخدام الطريقة self التي تعيد كائن الخيط المقابل للخيط الحالي

my $self = threads->self;

وبهذا الكائن نستطع استدعاء اي طريقة للخيط الحالي مثل tid التي تعيد رقم الخيط :

my $self_id = $self->tid;

أونستطيع إستدعاء الطريقتين في حالة واحدة بالشكل :

my $self_id = threads->self->tid;

في بعض الظروف الغير ملائمة قد تشترك معرف الخيط thread ID نفس المعرف لإكثر من كائن خيط ، يمكن فحص المساوي بمقارنة الـمعرفات IDs المكافئة ، لكن الافضل إستخدام الطريقة equals :

print "Equal!\n" if $self->equal($thread);
#same 
print "Equal!\n" if $thread->equal($self);


عندما نتعامل مع الخيوط يفيد Thread id في معرفة الخيط المحدد، يمكن ايضاً استخدام المعرفات في أغراض عديدة كإدراج بعض البيانات مع الخيط المحدد واستخراجها لاحقاً.


مشاركة البيانات بين الخيوط

هناك إختلاف رائيسي بين تضمين الخيوط القديمة في البيرل 5.6 وبين ithreads المتؤفرة في البيرل 5.8 ، في النمط القديم تتشارك البيانات تلقائياً ، ان اسلوب دعم الـخيط thread القديمة منجزة بواسطة نظام التشغيل لكن في أغلب الضروف ليس هناك سبباً لكي تحاكي البيرل هذا الاسلوب مع جميع الخيوط خاصة بنا ،بدلاً منه يتعقب الـمفسر interpreter إستعمال البيانات و يكرر البيانات بين الخيوط الغير مشاركة حسب الحاجة بواسطة وحـدة threads ، منطقياً النمط القديم بالفعل اكثر سهولة على الأقل في كتابة الخيوط واكثر كفاءة لكنها ليست أمانة بمعنى لايمكن ان نتأكد مع الخيوط الافتراضية ابداً من تداخل كتابة البيانات بشكل غير مقصود مع بعضهم البعض وأمان يسبب عادتاً ضياع البيانات.

تستخدم الوحدة البرمجية threads::shared لمشاركة البيانات الفعلية التي تؤفر الدالة share لجعل المتغير مشارك عبر جميع الخيوط ، في الواقع تقوم على آغلاق تكرار البيانات بفكره يستجيب المفسرinterpreter على تنفيذها بشكل عأدي بإشارة الى المتغيرات فقط .

أذ لم يتم شحن وحـدة threads مسبقاً فالدالة share والدوال الاخرى مثلlock و cond_wait ( المزودة ايضاً بهذة الوحدة ) تصدر كؤظائف غير متاصلة بمعنى أن البرنامج لايزال يشغل في سياق الغير مخيط nonthreaded لذا يجب التأكد من شحن threads أولاً :

use threads;
use threads::shared;

عند مشاركة المتغير فأن مجال الرؤية ينظر الية من على جميع خيط ولة نفس الموقع الاصلي بدلاً من عمل نسخة خاصة لكل خيط :

my $answer = 42;
share($answer);
my @array = (1,2,3);
share(@array);

يمكن استخدام حالة عامة بديلة بإستعمال الـصفه attribute المسمى shared عند تعريف المتغيرات ، في هذا التنسيق يلي اسم المتغير (المصفوفة ، الهاش ، الدالة ) الرمز : ثم توضع الـصفات attributes بعد ذلك

my $answer : shared = 42;
my @array : shared = qw(1 2 3);
my %hash : shared = (one => 1, two => 2, three => 3);

يمكن إنشاء مرجع مخفئ جديد ومشاركة الجميع في ذاهب واحد ، على اي حال الدالة تتوقع عادة وسيط متغير لذا يجب ان نستدعيها مع & لمنع الـprototype :

my $aref = &share([1 2 3]);

فبأي طريقة يمكن لجميع الخيوط الوصول للمتغير المشارك ولكي نمنع من كتابة بعضهم البعض وتنسيق الوصول يلزمنا وضع حماية بعمل قفل lock لكي تصل اليها فقط خيط واحدة بإي لحظة معينة .

■آليات القفل

بالرغم من ان مشاركة البيانات بين الخيوط Multithreading لها فوائد كبيرة ، الإ هناك عائق خطير أيضاً يمكن أن يسبب ضياع التحديثات ، قد يحدث للخيوط مشاكل عندما توقف أو توجل تنفيذها فمثلاً حالما يحاول خيطان لتعديل نفس المتغير بشكل آني، يمكننا تجاهل هذة المشكلة تلقائياً عندما البيانات غير مشاركة بين الخيوط ولكن مازلت المشكلة موجودة عند إشتراك المصادر المشتركة بين الخيوط المجتمعة ،يقدم هذا القسم مشكلة السباق race condition ولحسن الحظ يوجد عدة حلول للمشكلة لتصبح متزامنة الخيوط منها آليات القفل .


إقفال المتغير

القفل (lock) هو عنصر تحكم موضوع في البيرل لحجز البيانات بحيث يستطيع خيط واحد فقط الوصول اليه.عندما تكون البيانات مقفلة لاتستطيع اي خيط أخر من الوصول الى البيانات إلى ان يتم إفلات القفل .وإي خيط اخر تحاول الوصول ستوضع في حالة انتظار القفل (lock wait) وسيتم تأخير الخيط إلى أن يفلت القفل .

الدالة lock تأخذ إي متغير كالوسيط وتضع قفل علية لكي لايستطيع خيط اخر أن يقفلة طالما ان القفل موجود ولم يخرج من مجال رؤيتة ( يعرف في الغالب كمتغير محلي في مجال lexical scope) .على سبيل المثال في دالة writelog يأخذ مقبض الملف sharedfh المستخدم للكتابة قفلاًً لكي يتمكن خيط واحد فقط من الكتابة الى هذا المقبض في نفس الوقت :

my $sharedfh : shared = IO::File->open("> app.log");

sub writelog {
   lock $sharedfh;
   print $sharedfh, @_;
}

من المشاكل في مشاركة البيانات بين المصادر احدها مشكلة السباق race condition وتقع هذة المشكلة حينما تكون وضع الخيطين (أو مشاركة أحد العمليات بين عدة مستخدمين )حرجة ، بحيث تنتج نتائج مختلفة تبعاً لأوامر متفرقة ، لتوضيح المشكلة بإعتبر المثال التالي:

use threads;
use threads::shared;

my $bytes_sent : shared = 0;

my $socket = IO::Socket->new(....);

sub send_data {
   my $data = shift
   my $bytes = $socket->syswrite($data);
   $bytes_sent += $bytes;
}

حيث ان المشكلة تحدث في اخر سطر من الدالة send_data بإزدياد المتغير $bytes_sent أذ لدية عـددة إرتباطات أثناء التشغيل مثل حدوث التالي :

هذة السلسلة من الأحداث لن تحدث في كل وقت وتحدث في اشكال غير مقدرة وقد يؤدي الى حدوث bugs ، ان مشكلة السباق تسبب بالوصول الغير متزامن unsynchronized للبيانات المشاركة وبدون توضيح آلية التزامن synchronization ليس هناك طريقة لضمان البيانات المشاركة حينما تحدث بين الوصل الية وبين تعديل القيمة ، ولمعالج ذلك كما اشرنا سابقاً الدالة lock لإقفال المتغير $bytes_sent قبل محاولة إستعماله ، بهذا التعديل يصبح المثال الصحيح :

my $bytes_sent : shared = 0;

my $socket = IO::Socket->new(....);

sub send_data {
   my $data = shift
   my $bytes = $socket->syswrite($data);
   lock($bytes_sent);
   $bytes_sent += $bytes;
}


معرفة الفرق بين القفل والوصول شيء مهم جداًَ فإي خيط يمكنها الوصول الى المتغير بعدم الإهتمام بإمر إقفالة، لذا فأن طريقة القفل جيدة فقط أذا كانت جميع الخيوط ملتزمة بة كالـدالة flockمثلاً للمقابض الملفات ، هكذا فأن إقفال متغير الخيط فهو إستشاري (advisory) وليس إجباري (compelled). بمعنى ان الدالة lock تنشأ القفل الإستشاري على المتغير ، أي أن القفل الإستشاري يمنع أي خيط اخر من إستدعئ الدالة lock لإقفال المتغير حتي يترك الخيط الذي يحمل القفل الحالي ، على اي حال هذا القفل لايمنع الوصول الى المتغير التي يمكن ان يكون مازالت تقرأ أو تكتب الية ، عموماً يستعمل هذا القفل لمنع خيطان من محاولة تعديل نفس المتغير في نفس الوقت . عندما يحاول خيط اخر اقفال المتغير فأن الخيط الاخر يؤجل حتي يحين وقت القفل المتوفر ، يعني ان القفل يبقى في النمط force حتي يخرج القفل من مجال رؤيتة ، في مثالنا السابق المتغير $bytes_sent يقفل قبل زيادتة ويبقى في force طوال مجال الرؤية الدالة send_data.

ليس من الضرورة في جميع الاحوال أعطى قفل عند مشاركة البيانات بين مجموعة الخيوط بحيث يمكننا قراءة البيانات نفسها بدون إحتمال التضارب ، مع ذلك من الضروة وضع قفل للبيانات التي تكتبة أو تعدل فية ، فأذا كتبنا برنامج لخيط واحد فقط ولانرغب في تعديل البيانات المشاركة فاطلاقاً لان نحتاج القفل على الجميع ، وأن كان لدينا ثلاثة متغيرات مشاركة وجميعهم يضمنون في صفقة واحدة نستطيع أن نقفل واحد منهم فقط لسيطرة على الوصول لكل الثلاثة أو إنشاء متغير جديد فقط للغرض الإقفال —لايهتم مانستعمل طالما نعمل بثبات —

ايضاً ليس من الضرورة فتح القفل unlock للمتغير بشكل واضح ، لان القفل يحرر released حالما يخرج من مجال رؤيتة .ايضاًُ يمكن وضع الأقفال للمصفوفة أو هاش او globs داخل القفزات و التكرارت والشروط وبلوكات( map و grep والكلمة المحجوزة eval) :

lock @array;
lock %hash;
lock *glob;

نستطيع إقفال عنصر فقط لسجل مثلاً تعريف سجل كالعنصر مصفوفة أو قيمة هاش :

my %ghash : shared;

sub varlocksub {
my $key = shift;
lock $ghash{$key};
...
}

عنصر واحد يقفل من الهاش لذلك يمكن لاي خيط اخر أن تمر الى الدالة varlocksub مع مفتاح اخر مختلف وقيمة مطابقة في نفس الوقت ، اذا أتى الخيط بنفس المفتاح فسوف يجد القيمة تحت القفل والمفتاح ويتوجب علية الإنتظار . بينما من الممكن وضع القفل على الهاش أو المصفوفة ، حيث الدالة lock تتعامل مع المراجع بعنى ان الهاش المقفل لايدلل على إقفال إي من مفاتيحة ، هكذا المصفوفة المقفلة لاتدلل على إقفال إي من عناصرة ، بنفس الطريقة عند قفل المفتاح أو العنصر فهي لاتدلل على إقفال الهاش أو المصفوفة التي تعود اليها.

عندما نرغب بتغير أكثر من متغير في نفس الوقت يمكن استخدام متغير مستقل ( لايعمل شيء سواء يدير الوصول للمتغيرات الاخري ) في المثال هو المتغير $ok_to_update

sub send_data {
   my $data = shift	
   my $bytes = $socket->syswrite($data);
   lock($ok_to_update);
   $bytes_sent += $bytes;
   $bytes_left -= $bytes;
}

أو بواسطة إقفال الدالة بإظافة المدخل كالـ lock(\&subroutine) ( مع الخيوط القديمة ) ، عند إقفال الدالة فسوف تشغل خيط واحدة في كل وقت والخيوط الاخرى الوصلة الى الدالة توضع في إنتظار ، الافضل إقفال دالة لسرعة التنفيذ .

بالتخيط في نسخة البيرل 5.6 منجزة بصيغة احدث بواسطة إظافة attributes الى الدالة الخاصة بنا :

sub acknowledge: locked method { # thread safe
    my $self = shift;
    print $self->{socket} "200 OK\n";
    $self->{acknowledged}++;
}

عند إستعمال الخيوط في مجموعة من كائنات سيدعو الحاجة الى إقفال طرق الكائن للإقفال الكائن قبل تغييرة ، وغير ذلك فسوف يؤدي الى الفوضى بين خيطان عند تعديل الكائن بشكل آني ، فمثلاً في المثال إسفل ليست طريقة أمنة للخيط فقد يحاولان خيطان من تعديل الكائن $self بشكل آني

sub acknowledge { # NOT thread safe
    my $self = shift;
    print $self->{socket} "200 OK\n";
    $self->{acknowledged}++;
}

لذا يمكن في الخيط القديم أقفال الكائنات بضمن طرقة بشكل واضح كمايلي :

sub acknowledge { # thread safe
    my $self = shift;
    lock($self);
    print $self->{socket} "200 OK\n";
    $self->{acknowledged}++;
}

لكون $self هو مرجع ، فقد تتسأل من أستدعاء الدالة lock للقفل الكائن حيث ان الدالة تقوم بإقفال مرجع المتغير $self أو الأشياء التي يشير اليها ، بينما الخيوط الحديثة ithread ليست من الضروري للإقفال طرق methods الكائن لان الكائن ينشأ عموماً على كل thread basis ولن يشارك بين الخيوط — فأذا إحتجنا الى الوصول الى الطريقة منسقة الى متغيرات اخرى مشارك فأننا نقفل على تلك المتغيرات في أي حال من الأحوال ، فهناك حالياً بعض التقييدات في المشاركة للمرجع الكائن(blessed) لذا فأنة من غير المستحسن في إغلب الأحيان أن لا نشارك فيهم— شاهد صفحة manual للـthreads::shared للتفصيل ، كمثال اخرالدالة تقفل مقبض الملف المستعمل للخرج ، لكي يستطيع خيط واحد فقط لإكتابة الى المقبض في كل وقت :

my $sharedfh : shared = IO::File->open("> app.log");

sub writelog {
lock $sharedfh;
print $sharedfh @_;
}

فخصائص المشاركة والإقفال هنا للمتغير لايهتم ماالذي أو عندما نخصص الى المتغير أو اذ خصصنا الية على الجميع فهو إمان بعمل القفل وأن عمل لة blessed من مرجع glob ،لكن يفقد بين الخيوط ولانحصل على طرق IO::File المستدعئ


إقفال الدالة مع ithreads

في البيرل 5.6 فأن attributes المسمى locked و method تنجزن وصول متسلسل الى الدالة فهذة الattributes غير مؤجود مع ithreads ولايمكن إقفال الدالة بواسطة المرجع ولكن يمكننا إقفال المتغير المرتبط مع كود المرجع للدالة المخفية :

my $subref : shared = sub {
lock $subref;
print "This is thread ",threads->self->tid,"\n";
}

فهذة صيغة لاتهم حقاُ حيث نستطيع أستخدام مجال رؤية المتغير ويعرف في البيرل بالغلق(closure) ، فعلى سبيل المثال :

#!/usr/bin/perl -w
# locksub.pl
use strict;
use warnings;
use threads;
use threads::shared;

{ my $lock : shared;
  sub mysub {
      print "Thread ",threads->self->tid()," waiting for access\n";
      lock $lock;
      print "I am thread ",threads->self->tid(),"\n";
      sleep 1;
      threads->detach();
  }
}

#print "starting \n";
foreach (1..5) {
    threads->create(\&mysub);
}
#print "ending \n";


do {
    sleep 1;
    print "Waiting for threads:",(map { " ".$_->tid } threads->list),"\n";
} while (threads->list);

فالـclosure يخفي متغير القفل $lock حيث ان الكود الخارجي (الرئيسي ) لايستطع الحصول علية، وهنا كل خيط يحاول لتنفيذ الدالة mysub ، لكن الخيط 1 يقوم بتحميل القفل على متغير الـclosure في أي وقت كان لكؤن الخيوط يصلون الى الدالة mysub بشكل متسلسل ،ثم كل خيط ينام لثانية وبعد ذلك يفصل detaches نفسة ليدع الخيط الرئيسية main thread لمعرفة بأنة قد تمت وأكتمل ، في هذة الأثناء فأن الخيط الرئيسي سوف تنتظر الى جميع خيوط الإطفال للفصل قبل الخروج منها ، وهذا السيناريو في إدارة خيط بسيطة ومما لاشك فية فأن semaphores و queues اكثر تقنيات تطوراً sophisticated techniques ، الطريقة list لكي تعيد قائمة من الكائنات للخيط حيث تتضمن القائمة على هولاء التي هم مشغولين وايضاً على المنتهين


إدرة الخيط

تحتفظ البيرل بالقائمة الخيوط التي تنشأ من خلال طريقة الطبقة threads->list حيث تنسخ قائمة الخيوط :

@threads = threads->list;

وليجاد الخيط المطابق للأحد هذة الخيوط بواسطة الطريقة equal :

$self = threads->self;
foreach (@threads) {
next if $self->equal($_);
...
}

بسبب انها الخيط الحالية وذلك لايعني بأن تكون هذة الخيط في حالة التشغيل ، على اي حال البيرل تحتفظ بالسجل القيمة المعادة لكل خيط عند خروجها وسجل الخيط طالما تلك القيمة تبقى غير مطلوبة ، وهذا مشابة للعمليات الطفل child processes التي لا تملك waitpid للإستدعاهم ، فالخيوط بشكل مشابة للـ waitpid من خلال الطريقة join التي تستدعئ على الخيط التي نريد للإسترجاع القيمة قبل الخروج من الخيط الرئيسية :

my $return_result = $thread->join;

الطريقة join تمنع block الخيط الى المواصلة والي الوصول الى الخروج ،فأذا الخيط إجهضت aborted (مثلاً كاستدعاء die ) فسوف يتولد خطأ الى الخيط التي إستدعت join ، فهذا يعني بأن الخيط يقتل نفسه die قبل الاكمال ولذلك يتم حمايتها بواسطة eval :

my $return_result = eval { $thread->join; }
if ($@) {
warn "Thread unraveled before completion\n";
}

لسهولة يتم وضع التركيب المشترك للوحدة Thread التي إيضاً تنجز الطريقة eval التي تلف join داخل eval بهذا الشكل :

my $return_result = $thread->eval;
if ($@) {
warn "Thread unraveled before completion\n";
}

فألشكل السيء هي بتجاهل القيمة المعادة من الخيط لكونها قائمة مبعثرة بخيوط ميتة dead ، وأذا لم نهتم بالقيمة العاءدة فأننا نستطيع إخبار الخيط في إطالت ومنع القيمة العادة لة بعمل فصل لها :

$thread->detach;

فهذة الخيط مكافئة من وضع عملية الطفل child process في مجموعة العملية process group الخاصة بة —حيث ان الأب لن يحصل ابداً على حالة الخروج عندما الطفل يخرج ، فالإمساك بالخيط الميتة لن يلاحظة المبرمج مالم يكن هنالك قائد الإشارة __DIE__ لتسجل ذلك ويفحص الـ threads->self للخيط المحتضرة، فاذا عملنا join للخيط محتضرة من الخيط الرئيسي بدون إجراءات وقائية فسيدعو القلق بمؤت التطبيق ككل .

على سبيل المثال للدالة join البرنامج يبدأ بخمس خيوط ثم عمل joins لكل منهم قبل العودة عند خروج:

#!/usr/bin/perl
# join.pl
use warnings;
use strict;
# check we have threads
BEGIN {
    use Config;
    die "No interpreter threads!\n" unless $Config{useithreads};
}
use threads;

# define a subroutine for threads to execute
sub threadsub {
    my $self = threads->self;
    print "Thread ", $self->tid, " started \n";
    sleep 10;
    print "Thread ", $self->tid, " ending \n";
}

# start up five threads, one second intervals
my @threads;
foreach (1..5) {
    push @threads, new threads \&threadsub;
    sleep 1;
}

# wait for the last thread started to end
while (my $thread = shift @threads) {
    print "Waiting for thread ", $thread -> tid, " to end... \n";
    $thread->join;
    print "Ended \n";
}

# exit
print "All threads done \n";

تستعمل الدالة join عندما نهتم بقيمة العودة في هذة الحالة نستعمل join بشكل بسيط لتجنب إنتهاء الخيط الرئيسية قبل الأوان فهي تنتظر الخيط حتي الخروج وتنظيف الخيط ، بينما detach لانهتم بالقيمة العادة والى متي تصبح الخيط منتهية .



Deadlock

الأقفال أداة مفيدة للوصول المتزامن الى البيانات وهي مفتاح للبيانات الأمنة لكن لايزال الإقفال لديها بعض المشاكل خصوصاً عند الإقفال المتعدد فإبعتبار الكود التالي :

use threads;
use threads::shared;
my $a : shared = 4;
my $b : shared = "foo";
my $thr1 = threads->new(sub {
    lock($a);
    sleep 20;
    lock($b);
});
my $thr2 = threads->new(sub {
    lock($b);
    sleep 20;
    lock($a);
});

يحدث للبرنامج تعليق حتي تقوم بإصدار مفتاح الإعتراض c^ ، فالطريقة لعدم التعليق هي بإقفال خيط من الخيطين ، ويحدث أن الخيط الاول سوف يبدأ بالقفل المتغير (a) ثم بعد النوم تبعتها الخيط الثانية بإقفال المتغير(b)، في هذة الأثناء فأذا تبع الخيط الاول بإقفال المتغير (b) فأن علية الإنتظار حتي تنتهي من تحرير القفل unlock للمتغير b وستكون الخيط block ، واذا تبع ذلك الخيط الثانية بإقفال (a) فأن علية الإنتظار حتي ينتهي من تحرير القفل للمتغير a ، وبذلك فأن الخيط الاول ينتظر الخيط الثاني والخيط الثاني ينتظر الخيط الاول ، لذلك فكلاً من الخيطين لن تنفذ .

يسمى هذا الشرط بالعناق المميت deadlock وتعرف بأنها حالة إنتظار دائمة تقع حينما يحاول خيطان أو اكثر للحصول على الأقفال المرتبطة أصلاً ببعضهم البعض ، فكل خيط سوف تؤقف block وتنظر للاخر لتحرير القفل على المصدر وهذا مالايحدث ابداً في البرنامج الا أن هنالك عددة طرق لمعالجة ذلك النوع من المشاكل وافضل طريقة هي الربط المسبق للإقفال في نفس الطلب تماماً ، مثلاً إقفال المتغيرات $a, $b, $c فعليك اقفال a قبل b ، و إقفال b قبل c وهكذا بفترة قصيرة من الؤقت للتقليل من العناق المميت وهذا الحل لدية بعض العيوب حيث للمستخدم أن لايعلم أوقات وفترات إرتباط كل مصدر وذلك قبل بدأ التنفيذ وبالتالي يصعب تحديد عملية الربط المسبق الطريقة الاخرى بإستخدام السيمافور .



Condition Variables, Semaphores, and Queues

تستخدم المتغيرات المقفلة في اكثر البرامج سهولتاً للسيطرة على الوصول والتنفيذ ، كما واننا نستطيع استخدامها ايضاً في البلوكات الشرطية بإخذ الخيوط المنتظرة على المتغير حتي يعطى إشارة الإمضاء . بينما المتغير في مصلح condition variable يأخذ عدة قواعد للسطر البداية starting line ، فبكل سطر خيط على البلوك (مثلاً ) حتي مسدس البداية starting pistol يطلق بواسطة خيط اخر ، يعتمد على نوع الإشارة المرسلة او التي إرسلنها ، فأما خيط الوحيد تعطي بإشارة البداء go-ahead للإستمرار اوكل الخيط هي من تعطى . فالمتغيرات الشرط Condition variables هي إداة قوية لتنظيم الخيوط ويمنح لنا السيطرة على تدفق البيانات في تطبيقات المخيطة ويمنع الخيوط من الوصول الى البيانات المشاركة في إسلوب الغير متزامن unsynchronized، تعتبر ايضاُ أساساً للإنواع الاخر للتفاعلات الخيط ، كما يمكننا إستخدام السيمافورات مع الخيوط عن طريف الوحـدة البرمجية Thread::Semaphore لتحكم بين الخيوط ، ايضاً أكثر تحكم بتضمين الطابور عن طريق الوحـدة البرمجية Thread::Queue لتنظم االمهام بين الخيوط .كلتا الوحدات السابقة مبنية أساساً على ميزات متغيرات الشرط condition variables لإنجاز ؤظائفهم لكن في الجوهر فأن تغلفهم فيه أكثر سهولة .للنعرف كيف كل هذة تعملون فسوف نضمن الـbasic لكن التطبيقات المخيطة functional أولاًُ سوف نتعرف على متغيرات الشرط مباشرتاً ثم بعد ذلك أستخدام السيمافورات واخيرناً إستخدام الطابور .


Condition Variables

توفر البيرل طريقة اخرى لآليات تزامن الخيوط بواسطة متغيرات الشرط يستمر التماثل analogy لسطر البداء starting line ، للخيوط “line up” (يستقض) على المتغير المقفل نستعمل الدالة cond_wait ، تأخذ هذة الدالة المتغير المقفل كالوسيط ويفتح قفلة unlocks ، ومن ثم بعد ذلك تنتظر حتي تستلم الإشارة signal من خيط اخر ، وعند إستلام الإشارة فأن الخيط يستأنف التنفيذ ويعيد إقفال المتغير .

أن كانت لدينا عدة خيوط وجميعهم ينتظرون على نفس المتغير ، فأننا سوف نحتاج فقط بأن نقفل lock كل خيط ثم بعد ذلك (إنتظار شرط) cond_wait المتغير ، ولكؤن القفل يمنع من تنفيذ الدالة cond_wait لاكثر من خيط واحد في نفس الوقت ، فالعملية تعالج بشكل آلي لنا ، بنظر الى الكود التالي المقتطف لتقنية الأساسية المطبقة للـ pool الخيوط pool of threads :

my $lockvar;   # lock variable - note it is not locked at this point

sub my_waiting_thread {
# wait for signal
{
lock $lockvar;
cond_wait $lockvar;
}

# ...the rest of the thread, where the work is done
}

for (1..10) {
threads->create(\&my_waiting_thread);
}

فهذا الكود المقصوص من 10 خيوط ، جمعيها تستعمل نفس الدالة subroutine كنقطة دخولهم entry point ،ويقفل كل واحد ثم بعد ذلك ينتظر على المتغير $lockvar حتي يستلم الإشارة ، ولكوننا لانرغب بإحتفاظ بالقفل على المتغير بعد مغادرتنا cond_wait ، فسوف نضع كلتا الدالتين lock و cond_wait داخل البلوك بتاعهم للتحديد المجال القفل ، وهذا مهم لكون الخيوط الاخر لايمكنها ان تدخل في حالة الإنتظار بينما لدينا القفل على المتغير ، فأحياناً ذلك ما نرغب فية عند كتابة السكربت ولكن في أغلب الأحيان ليس كذلك.

بعد أن نثبتestablished الـpool للإنتظار الخيوط فسوف نحتاج الى إرسال الإشارة لإيقاظ احدهم والتي يمكن ان نعملها مع

cond_signal :

# wake up one thread
cond_signal $lockvar;

فبهذا سيفتح unlock خيط واحد منتظرة على الـcondition variable ، فالخيط تلك تعيد البداء restarted بشكل عشوائي ، ولانستطيع إفتراض الخيط الاول من عمل له block وسيكون الأول لكي يفتح unlocked مرة اخرى ، هذة ملائمة عندما يكون لدينا الـ pool of threads على disposal بتعنا ، فجميعها تؤدين الى نفس الوظيفة الأساسية ، بدلاً من ذلك ، نستطيع فتح القفل unlock لجميع الخيوط مرة واحدة بواسطة إستدعى cond_broadcast هكذا:

# everybody up!
cond_broadcast $lockvar; 

فهذا يرسل الرسائلة لكل خيط تنتظر على ذلك المتغير والتي هي تلائم للظروف حيث المصدر المشترك common resource هو متؤفر بشكل مشروط ونحن نرغب لإيقاف او بداء stop or start جميع الخيوط ، يعتمد على سواءاً هم متؤفر او غير متؤفر ، وهو مهم للإدراك ، على اية حال الذي اذ الخيوط لا فهم ينتظرون على المتغير ، والإشارة منبوذة او مستبعدة ;فهي لم تحفظ حتي الخيط يستعد الى الرد علية ، من المهم ايضا إدراك بأن هذة ليس لها شيء (directly) لفعل مع إشارات العملية ، كمعالجة بواسطة مصفوفة الـ%SIG ; فكتابة التطبيق المخيط لمعالجة إشاراءت العملية مهمة اكثر تعقيداً ( شاهد الموديل Thread::Signal لإكثر تفصيل ) لاحظ بأن تلك القيمة الفعلية من متغير القفل هي كلياً غير متعلقة irrelevant لهذة العملية ، لذا نستطيع إستعمالها للإشياء اخرى فعلى سبيل المثال يمكننا أن نستعملها للعبور القيمة الى الخيط في الحظة التي نشيرsignal إلية ، فالتطبيق المشاهد اسفل يعمل ذلك بإستعمال pool لخيوط الخدمة لمعالجة السطور الدخل المارة إليهم بواسطة الـmain thread.بينما يفحصة ، يراعئ إغلاق الدفع للمتغيران الشرط التي يطرحن في قلب التطبيق :

تسمح 2 متغيرات شرط للخيط الرئيسي و الـpool من خيوط المخدمة للتعاون cooperate مع بعضهم البعض ، هذا يعين لكل سطر يقراء بالخيط الرئيسي المارة الى خيط مخدوم واحد بسرغة وبأمان:

#!/usr/bin/perl
# threadpool.pl
use warnings;
use strict;

use threads;
use threads::shared;

my $threads = 3; # number of service threads to create
my $line : shared= "";
                 # parent lock variable and input line set to "" here, we
                 # assign each new line of input to it, and set it to 'undef'
                 # when we are finished to tell service threads to quit
my $pool : shared = 0;
                 # child lock variable and pool counter set to 0 here,
                 # service threads increment it when they are ready for input

# a locked print subroutine ? stops thread output mingling
{
  my $lock : shared;
  sub thr_print {
      lock $lock;
      print @_;
  }
}

# create a pool of three service threads
foreach (1..$threads) {
    threads->create(\&process_thing);
}

# main loop: Read a line, wait for a service thread to become available,
# signal that a new line is ready, then wait for whichever thread picked
# up the line to signal to continue
while ($line = <>) {
    chomp $line;
    thr_print "Main thread got '$line'\n";

    # do not signal until at least one thread is ready
    if ($pool==0) {
        thr_print "Main thread has no service threads available, yielding\n";
        threads->yield until $pool>0;
    }
    thr_print "Main thread has $pool service threads available\n";

    # signal that a new line is ready
    {
        lock $pool;
        cond_signal $pool;
    }
    thr_print "Main thread sent signal, waiting to be signaled\n";
    # wait for whichever thread wakes up to signal us
    {
        lock $line;
        cond_wait $line;
    }
    thr_print "Main thread received signal, reading next line\n";
}

thr_print "All lines processed, sending end signal\n";
# set the line to special value 'undef' to indicate end of input
$line = undef;
{
    lock $pool;
    # tell all threads to pick up this 'line' so they all quit
    cond_broadcast $pool;
}
threads->join($_) foreach threads->list();
thr_print "Main thread ended\n";
exit 0;

# the thread subroutine ? block on lock variable until work arrives
sub process_thing {
    my $self=threads->self;
    my $thread_line;

    thr_print "Thread ",$self->tid," started\n";
    while (1) {
        # has the 'quit' signal been sent while we were busy?
        last unless (defined $line);

        # wait to be woken up
        thr_print "Thread ",$self->tid," waiting\n";
        {
            lock $pool;
            $pool++;
            cond_wait $pool; #all threads wait here for signal
            $pool--;
        }

        # retrieve value to process
        thr_print "Thread ",$self->tid," signaled\n";
        $thread_line = $line;

        # was this the 'quit' signal? Check the value sent
        last unless (defined $thread_line);

        # let main thread know we have got the value
        thr_print "Thread ",$self->tid," retrieved data, signaling main\n";
        {
            lock $line;
            cond_signal $line;
        }

        # do private spurious things to it
        chomp ($thread_line=uc($thread_line));
        thr_print "Thread ",$self->tid," got '$thread_line'\n";
    }
    thr_print "Thread ",$self->tid," ended\n";
}

الفكرة الأساسية للمتغير الشرط مفهومة ، فالطريقة التي يعمل بها التطبيق واضحة ,على اي حال فهناك بعض التأشيرات مازالت غير مفهومة وبشكل خاص المتغير $pool المستعمل في الخيط الرئيسي للضمان والتي ترسل إشارة فقط عندما تؤجد هنالك خيط مخدومة منتظرة للإستلامة ، ولإنجاز هذا فأن المتغير $pool يزتأد مباشرتاً قبل cond_wait ويتناقص مباشرتاً بعد ذلك .فعند عمل هذا الشيء ، سنضمن من أن $pool يعكس بدقة الرقم للخيوط المخدومة المنتظرة ، فأن كأن بقيمة 0 فأن الخيط الرئيسي يستعمل yield للعبور التنفيذ حتى يصبح الخيط المخدوم متؤفر مرة اخرى .

باقي .............

فهو يمعمل بشكل مماثل لشكل الوسيط الاول ماعدا لتقسيم المسؤوليات ، فالهدف هو ليسمح لنا للإشارة signal الى نفس المتغير عددة اوقات وفتح القفل unlock اكثر من خيط واحدة concurrently ، بإمتلاك الخيوط المختلفة تستعمل متغيرات القفل المختلفة للنفس المتغير الإشارة signal variable . فرقم المتغيرات القفل المختلفة في اللعب هي مساوية الى الرقم الكلي للخيوط الـ concurrent (متغير الإشارة يمكن أن يطلق ) .

ايضاً في البيرل 5.8.3 وما بعد فالوحدة threads تزود إيضاً بالدالة cond_timedwait وهي تعمل بشكل مماثل untimed counterpart بتاعة كما في السابق ، لكنها تأخذ قيمة إظافية وهي قيمة الtimeout في الثواني ، والقيمة العادة من cond_timedwait هي true اذا الإشارة إستلمت و false إذا الـtimeout وصل أولاً ، فهذا يدعنا لكتابة الكود التي يمكن ان يوقف stop الإنتظار بفترة زمنية لعمل المهام الاخرى :

{
lock $line;
while (! cond_timedwait($line,10)) {
thr_print "Yawn...\n";
}
}


Semaphores

بالرغم من ان التطبيق السابق يعمل مانحتاج الية الا ان كتابة البرنامج أكثر تعقيداً ونسطيع استعمال الـ semaphores لإكثر سهولة ، فإغلب الخيوط مهما كانت اللغة أو بيئة التشغيل المدعمة لها فأن البيرل لاتخلف عن ما تطرقنا فية سابقاً مع الـIPC semaphores والتي هي مشابة تماماًُ للـthread semaphores ، فمبدئياً فأن اعلامهم العددية تأخذ القيمة صفر أو أي عدد مؤجب ويطبق مع القواعد التالية:

البيرل تدعم الـthread semaphores بواسطة الموديل المسمى Thread::Semaphor والتي تضمن الـsemaphores في مصطلح متغيرات الشرط — وكما هو الكود أسفل التي تنجز من طريقة واحدة new للطبقة ، وطريقتان للكائن up and down :

$semaphore = new Thread::Semaphore;    # create semaphore, initial value 1
$semaphore2 = new Thread::Semaphore(0) # create semaphore, initial value 0

up increments a semaphore:
$semaphore->up;      # increment semaphore by 1
$semaphore->up(5);   # increment semaphore by 5

Finally, down decrements a semaphore, blocking if necessary:
$semaphore->down;    # decrement semaphore by 1
$semaphore->down(5); # decrement semaphore by 5

بإعتماد على مانريد بإستعمال الـsemaphores كثنائي وذلك للتؤقف والإستمرار أو يمنح